# LeetCode 474、一和零

# 一、题目描述

给你一个二进制字符串数组 strs 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。 

提示:

  • 1 <= strs.length <= 600
  • 1 <= strs[i].length <= 100
  • strs[i] 仅由 '0''1' 组成
  • 1 <= m, n <= 100

# 二、题目解析

# 三、参考代码

# 1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 微信:wzb_3377
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 一和零(LeetCode 474):https://leetcode.cn/problems/ones-and-zeroes/
class Solution {
    public int findMaxForm(String[] strs, int m, int n) {

        // 获取字符串数组的长度
        int len = strs.length;
        
        // dp[i][j][k] 表示
        // 1、从前 i 个字符串中选出若干个
        // 2、在使得其中 0 的数目不超过 j
        // 3、1 的数目不超过 k 的情况下能够选出的最多的字符串数目
        int[][][] dp = new int[len + 1][m + 1][n + 1];

        // 开始填充这个三维数组
        // 1、物品一个一个尝试
        // 2、容量一点一点尝试
        // 3、每个物品分类讨论:选与不选
        for (int i = 1; i <= len; i++) {

            // 注意到 i 是从下标 1 开始访问的
            // 对于字符串数组 strs 来说,区间 [ 0 , 1 ] 的这个字符串是 strs[0]
            // 也就是说,区间 [ 0 , i ] 的最后一个字符串是 strs[i - 1]
            // 需要讨论 strs[i - 1] 能否加入到我们的【背包当中】
            String currentStr = strs[ i - 1 ];
            
            // 统计 strs[ i - 1 ] 中 0 的数量
            int zeroNum = countZeroNumber(currentStr);

            // 统计 strs[ i - 1 ] 中 1 的数量
            int oneNum  = countOneNumber(currentStr);

            // 01 背包问题,开始讨论
            for( int j = 0 ; j <= m ; j++){

                for( int k = 0 ; k <= n ; k++){
                    
                    // 1、如果发现目前能够使用的 0 的个数 j 小于了当前字符串中 0 的数量
                    // 2、如果发现目前能够使用的 1 的个数 k 小于了当前字符串中 1 的数量
                    if( j < zeroNum || k < oneNum){
                        
                        // 意味着当前这个字符串 currentStr 无法放入到背包当中
                        // 此时,不选择当前考虑的这个字符串
                        // 能够得到的字符串数目的最大值可以通过前 i - 1 个字符串中得到结果
                        dp[ i ][ j ][ k ] = dp[ i - 1 ][ j ][ k ];

                    // 否则,当前这个字符串有资格加入都背包当中
                    }else{
                        
                        // 在有资格的情况下,可以选,也可以不选
                        // 1、不选, 能够得到的字符串数目的最大值可以通过前 i - 1 个字符串中得到结果
                        // 2、选,那么当前这个字符串必然加入进去,子集长度加 1
                        // 同时 0 的个数剩下 j - zeroNum,1 的个数剩下 k - oneNum
                        // 需要从前 i - 1 个字符串中去凑这些 0 和 1
                        dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zeroNum][k - oneNum] + 1);
                    }
                }
            }
        }

        // 返回结果
        return dp[len][m][n];
    }

    // 统计字符串 str 中 0 的个数
    private int countZeroNumber(String str){

        int count = 0;

        for(char c : str.toCharArray()){

            if( c == '0'){

                count++;

            }

        }
        return count;

    }

    // 统计字符串 str 中 1 的个数
    private int countOneNumber(String str){

        int count = 0;

        for(char c : str.toCharArray()){

            if( c == '1'){

                count++;

            }

        }
        return count;

    }

}

# **2、**C++ 代码

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {

        // 获取字符串数组的长度
        int len = strs.size();
        
        // dp[i][j][k] 表示
        // 1、从前 i 个字符串中选出若干个
        // 2、在使得其中 0 的数目不超过 j
        // 3、1 的数目不超过 k 的情况下能够选出的最多的字符串数目
        vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(m + 1, vector<int>(n + 1)));

        // 开始填充这个三维数组
        // 1、物品一个一个尝试
        // 2、容量一点一点尝试
        // 3、每个物品分类讨论:选与不选
        for (int i = 1; i <= len; i++) {

            // 注意到 i 是从下标 1 开始访问的
            // 对于字符串数组 strs 来说,区间 [ 0 , 1 ] 的这个字符串是 strs[0]
            // 也就是说,区间 [ 0 , i ] 的最后一个字符串是 strs[i - 1]
            // 需要讨论 strs[i - 1] 能否加入到我们的【背包当中】
            string currentStr = strs[ i - 1 ];
            
            // 统计 strs[ i - 1 ] 中 0 的数量
            int zeroNum = countZeroNumber(currentStr);

            // 统计 strs[ i - 1 ] 中 1 的数量
            int oneNum  = countOneNumber(currentStr);

            // 01 背包问题,开始讨论
            for( int j = 0 ; j <= m ; j++){

                for( int k = 0 ; k <= n ; k++){
                    
                    // 1、如果发现目前能够使用的 0 的个数 j 小于了当前字符串中 0 的数量
                    // 2、如果发现目前能够使用的 1 的个数 k 小于了当前字符串中 1 的数量
                    if( j < zeroNum || k < oneNum){
                        
                        // 意味着当前这个字符串 currentStr 无法放入到背包当中
                        // 此时,不选择当前考虑的这个字符串
                        // 能够得到的字符串数目的最大值可以通过前 i - 1 个字符串中得到结果
                        dp[ i ][ j ][ k ] = dp[ i - 1 ][ j ][ k ];

                    // 否则,当前这个字符串有资格加入都背包当中
                    }else{
                        
                        // 在有资格的情况下,可以选,也可以不选
                        // 1、不选, 能够得到的字符串数目的最大值可以通过前 i - 1 个字符串中得到结果
                        // 2、选,那么当前这个字符串必然加入进去,子集长度加 1
                        // 同时 0 的个数剩下 j - zeroNum,1 的个数剩下 k - oneNum
                        // 需要从前 i - 1 个字符串中去凑这些 0 和 1
                        dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - zeroNum][k - oneNum] + 1);
                    }
                }
            }
        }

        // 返回结果
        return dp[len][m][n];
        

    }

private:
    int countZeroNumber(string str){

        int count = 0;

        for(int i = 0 ; i < str.size() ; i++){

            if( str[i] == '0'){

                count++;

            }

        }
        return count;
    }

private:
    int countOneNumber(string str){

        int count = 0;

        for(int i = 0 ; i < str.size() ; i++){

            if( str[i] == '1'){

                count++;

            }

        }
        return count;
    }

};

# 3、Python 代码

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:

        length = len(strs)

        # dp[i][j][k] 表示
        # 1、从前 i 个字符串中选出若干个
        # 2、在使得其中 0 的数目不超过 j
        # 3、1 的数目不超过 k 的情况下能够选出的最多的字符串数目
        dp = [[[0] * ( n + 1 ) for _ in range( m + 1)] for _ in range(length+1)]

        # 开始填充这个三维数组
        # 1、物品一个一个尝试
        # 2、容量一点一点尝试
        # 3、每个物品分类讨论:选与不选
        for i in range(1, length+1):

            # 注意到 i 是从下标 1 开始访问的
            # 对于字符串数组 strs 来说,区间 [ 0 , 1 ] 的这个字符串是 strs[0]
            # 也就是说,区间 [ 0 , i ] 的最后一个字符串是 strs[i - 1]
            # 需要讨论 strs[i - 1] 能否加入到我们的【背包当中】

            # 统计 strs[ i - 1 ] 中 0 的数量
            c0 = strs[i-1].count('0') 

            # 统计 strs[ i - 1 ] 中 1 的数量   
            c1 = len(strs[i-1]) - c0        
            for j in range(m+1):           
                for k in range(n+1): 
                    # 1、如果发现目前能够使用的 0 的个数 j 小于了当前字符串中 0 的数量
                    # 2、如果发现目前能够使用的 1 的个数 k 小于了当前字符串中 1 的数量      
                    if j < c0 or k < c1:    
                        dp[i][j][k] = dp[i-1][j][k]
                    else:   
                        # 在有资格的情况下,可以选,也可以不选
                        # 1、不选, 能够得到的字符串数目的最大值可以通过前 i - 1 个字符串中得到结果
                        # 2、选,那么当前这个字符串必然加入进去,子集长度加 1
                        # 同时 0 的个数剩下 j - zeroNum,1 的个数剩下 k - oneNum
                        # 需要从前 i - 1 个字符串中去凑这些 0 和 1                
                        dp[i][j][k] = max( dp[i-1][j][k], dp[i-1][j-c0][k-c1] + 1 )
        
        return dp[length][m][n]